﻿#include "FITKPartDataSetManager.h"

// VTK
#include <vtkUnstructuredGrid.h>
#include <vtkPolyData.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolygon.h>
#include <vtkIdList.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Component/FITKAbaqusData/FITKAbaqusPart.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaMeshPartVTK.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoCommand.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoShapeAgent.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKVirtualTopoManager.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshVS.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshEntity.h"

namespace Exchange
{
    // Inistailize the static instance.
    FITKPartDataSetManager* FITKPartDataSetManager::s_instance{ nullptr };

    FITKPartDataSetManager* FITKPartDataSetManager::getInstance()
    {
        if (!s_instance)
        {
            s_instance = new FITKPartDataSetManager;
        }

        return s_instance;
    }

    void FITKPartDataSetManager::Destory()
    {
        if (s_instance)
        {
            delete s_instance;
        }

        s_instance = nullptr;
    }

    FITKPartDataSetManager::~FITKPartDataSetManager()
    {
        // Delete all geometry data set only.
        for (const PartGeomDataVTK* data : m_partGeomDataHash.values())
        {
            if (!data)
            {
                continue;
            }

            if (data->DataSetVertex)
            {
                data->DataSetVertex->Delete();
            }

            if (data->DataSetEdge)
            {
                data->DataSetEdge->Delete();
            }

            if (data->DataSetFace)
            {
                data->DataSetFace->Delete();
            }

            delete data;
        }

        m_partGeomDataHash.clear();
    }

    vtkDataSet* FITKPartDataSetManager::getPartMeshDataSet(int partId, bool update)
    {
        // Get the part data.
        AbaqusData::FITKAbaqusPart* part = FITKDATAREPO->getTDataByID<AbaqusData::FITKAbaqusPart>(partId);
        if (!part)
        {
            return nullptr;
        }

        // Get mesh data from the part data.
        Interface::FITKAbaMeshPartVTK* meshData = part->getMesh();
        if (!meshData)
        {
            return nullptr;
        }

        if (update)
        {
            meshData->update();
        }

        return meshData->getGrid();
    }

    vtkDataSet* FITKPartDataSetManager::getPartGeomDataSet(int partId, int type, bool update)
    {
        PartGeomDataVTK* data{ nullptr };
        bool flag = getPartGeomData(partId, data, update);
        if (!flag)
        {
            return nullptr;
        }

        switch (type)
        {
        case 0:
        {
            return data->DataSetVertex;
        }
        case 1:
        {
            return data->DataSetEdge;
        }
        case 2:
        {
            return data->DataSetFace;
        }
        default:
            break;
        }

        return nullptr;
    }

    bool FITKPartDataSetManager::getPartGeomData(int partId, PartGeomDataVTK* & ioData, bool update)
    {
        // Get the part data.
        AbaqusData::FITKAbaqusPart* part = FITKDATAREPO->getTDataByID<AbaqusData::FITKAbaqusPart>(partId);
        if (!part)
        {
            return false;
        }

        Interface::FITKAbsGeoCommand* partCmd = part->getGeoCommandObj();
        Interface::FITKGeoCommandManager* partCmdMgr = dynamic_cast<Interface::FITKGeoCommandManager*>(partCmd);
        if (!partCmdMgr)
        {
            return false;
        }

        int partGeomId = partCmd->getDataObjectID();

        // Get the newest state id of the shape.
        Interface::FITKAbsGeoShapeAgent* shapeAgent = partCmd->getShapeAgent();
        QVariant newState;
        bool flag = shapeAgent->createShapeState(newState);

        PartGeomDataVTK* data{ nullptr };
        if (m_partGeomDataHash.contains(partGeomId))
        {
            data = m_partGeomDataHash.value(partGeomId);

            // Clear the history data except the datasets pointer.
            if (update || (data->ShapeState.isValid() && !shapeAgent->isSameState(newState, data->ShapeState)))
            {
                resetVtkObj(data->DataSetVertex);
                resetVtkObj(data->DataSetEdge);

                if (data->DataSetVertex)
                {
                    resetVtkObj(data->DataSetVertex->GetPoints());
                }

                // TODO 2024/12/30 Temp
                if (data->DataSetEdge)
                {
                    data->DataSetEdge->DeepCopy(vtkSmartPointer<vtkUnstructuredGrid>::New());
                    data->DataSetEdge->SetPoints(vtkSmartPointer<vtkPoints>::New());
                    // resetVtkObj(data->DataSetEdge->GetPoints());
                }

                vtkPolyData* facePoly = data->DataSetFace;
                if (facePoly)
                {
                    resetVtkObj(facePoly);
                    resetVtkObj(facePoly->GetPoints());
                    resetVtkObj(facePoly->GetPolys());
                    resetVtkObj(facePoly->GetPointData()->GetNormals());
                }

                data->ShapeState = QVariant();
                data->CellIdVertexIdMap.clear();
                data->CellIdEdgeIdMap.clear();
                data->CellIdFaceIdMap.clear();
                data->VertexIdCellIdMap.clear();
                data->EdgeIdCellIdsMap.clear();
                data->FaceIdCellIdsHash.clear();
                data->FaceSolidIdMap.clear();
                data->SolidFaceIdsHash.clear();
                data->VertexCellIdCmdIdMap.clear();
                data->EdgeCellIdCmdIdMap.clear();
                data->FaceCellIdCmdIdMap.clear();
            }
            // Return the history data.
            else
            {
                ioData = data;
                return true;
            }
        }
        else
        {
            // Insert the data to the dict.
            data = new PartGeomDataVTK;
            m_partGeomDataHash.insert(partGeomId, data);

            // Initialize vtk objects.
            data->DataSetVertex = vtkUnstructuredGrid::New();
            data->DataSetVertex->SetPoints(vtkSmartPointer<vtkPoints>::New());

            data->DataSetEdge = vtkUnstructuredGrid::New();
            data->DataSetEdge->SetPoints(vtkSmartPointer<vtkPoints>::New());

            data->DataSetFace = vtkPolyData::New();
            data->DataSetFace->SetPoints(vtkSmartPointer<vtkPoints>::New());
            data->DataSetFace->SetPolys(vtkSmartPointer<vtkCellArray>::New());

            // Connect the signal for releasing resource.
            connect(partCmd, &Interface::FITKAbsGeoCommand::dataObjectDestoried,
                this, &FITKPartDataSetManager::slot_partGeomDeleted, Qt::UniqueConnection);
        }

        // Initialize the hash code.
        data->ShapeState = newState;

        if (!flag)
        {
            return false;
        }

        // Old version.
        //@{
        // Create datasets for all none-referenced sub-commands.
        //int nSubCmd = partCmdMgr->getDataCount();
        //for (int i = 0; i < nSubCmd; i++)
        //{
        //    // Get the sub-command.
        //    Interface::FITKAbsGeoCommand* subCmd = partCmdMgr->getDataByIndex(i);
        //@}

        // New version.
        //@{
        // Create datasets for all none-referenced sub-commands.
        QList<Interface::FITKAbsGeoCommand*> rootSubCmds = partCmdMgr->getRootCommands();
        for (Interface::FITKAbsGeoCommand* subCmd : rootSubCmds)
        {
        //@}
            if (!subCmd)
            {
                continue;
            }

            // Check if the command has been referred.
            int nRefed = subCmd->getReferencedCmdCount();
            if (nRefed != 0)
            {
                continue;
            }

            // Get the abstract shape agent.
            Interface::FITKAbsGeoShapeAgent* shapeAgent = subCmd->getShapeAgent();
            if (!shapeAgent)
            {
                continue;
            }

            // Get the sub-command id.
            int subCmdId = subCmd->getDataObjectID();

            // Get the trianglation of the shape.
            Interface::FITKGeometryMeshVS* meshVS = shapeAgent->getMeshVS();
            Interface::FITKVirtualTopoManager* topoMgr = shapeAgent->getVirtualTopoManager();
            if (!meshVS || !topoMgr)
            {
                continue;
            }

            addVertexGrid(meshVS, data, subCmdId);
            addEdgeGrid(meshVS, data, subCmdId);
            addFaceGrid(meshVS, data, subCmdId);

            // Initialize solid faces.
            initializeSolidFaceMap(topoMgr, data);
        }

        // Calculate poly normals.
        if (data)
        {
            calculatePointNormals(data->DataSetFace);
        }

        // Save the data.
        ioData = data;

        // Update the pipeline.
        if (data->DataSetVertex)
        {
            data->DataSetVertex->Modified();
        }

        if (data->DataSetEdge)
        {
            data->DataSetEdge->Modified();
        }

        if (data->DataSetFace)
        {
            data->DataSetFace->Modified();
        }

        return true;
    }

    void FITKPartDataSetManager::addVertexGrid(Interface::FITKGeometryMeshVS* meshVS, PartGeomDataVTK* iData, int cmdId)
    {
        // Get or create the grid and the points.
        vtkUnstructuredGrid* ugrid = iData->DataSetVertex;
        if (!ugrid)
        {
            ugrid = vtkUnstructuredGrid::New();
            ugrid->SetPoints(vtkSmartPointer<vtkPoints>::New());
            iData->DataSetVertex = ugrid;
        }

        vtkPoints* points = ugrid->GetPoints();

        // Get all points.
        const QHash<int, Interface::FITKGeoMeshVSPt*> pointsHash = meshVS->getPointItems();
        QHashIterator<int, Interface::FITKGeoMeshVSPt*> iter(pointsHash);

        int ptIndex = -1;

        while (iter.hasNext())
        {
            iter.next();

            int meshPtId = iter.key();
            Interface::FITKGeoMeshVSPt* pt = iter.value();

            // Add point.
            ptIndex = points->InsertNextPoint(pt->x(), pt->y(), pt->z());

            // Add vertex cell.
            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(ptIndex);
            ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

            // Save the vertex id.
            iData->CellIdVertexIdMap.push_back(meshPtId);
            iData->VertexIdCellIdMap.insert(meshPtId, ptIndex);
            iData->VertexCellIdCmdIdMap.push_back(cmdId);
        }
    }

    void FITKPartDataSetManager::addEdgeGrid(Interface::FITKGeometryMeshVS* meshVS, PartGeomDataVTK* iData, int cmdId)
    {
        // Get or create the grid and the points.
        vtkUnstructuredGrid* ugrid = iData->DataSetEdge;
        if (!ugrid)
        {
            ugrid = vtkUnstructuredGrid::New();
            ugrid->SetPoints(vtkSmartPointer<vtkPoints>::New());
            iData->DataSetEdge = ugrid;
        }

        vtkPoints* points = ugrid->GetPoints();

        // Get all edges.
        const QHash<int, Interface::FITKGeoMeshVSEdgeEntity*> edgesHash = meshVS->getEdgeItems();
        QHashIterator<int, Interface::FITKGeoMeshVSEdgeEntity*> iter(edgesHash);

        int ptIndex = -1, cellIndex = -1;

        while (iter.hasNext())
        {
            iter.next();

            int meshEdgeId = iter.key();
            Interface::FITKGeoMeshVSEdgeEntity* edge = iter.value();

            // Get edge points.
            const QList<Interface::FITKGeoMeshVSPt*> & pts = edge->getPoints();

            // Create poly line cell.
            //@{
            vtkSmartPointer<vtkIdList> polyLineCell = vtkSmartPointer<vtkIdList>::New();

            for (Interface::FITKGeoMeshVSPt* pt : pts)
            {
                // Add point.
                ptIndex = points->InsertNextPoint(pt->x(), pt->y(), pt->z());
                polyLineCell->InsertNextId(ptIndex);
            }

            // Add cell.
            cellIndex = ugrid->InsertNextCell(VTKCellType::VTK_POLY_LINE, polyLineCell);

            // Save the edge id.
            iData->CellIdEdgeIdMap.push_back(meshEdgeId);
            iData->EdgeIdCellIdsMap[meshEdgeId].push_back(cellIndex);
            iData->EdgeCellIdCmdIdMap.push_back(cmdId);
            //@}

            // Create line cell.
            //@{
            //for (int i = 0; i < pts.count() - 1; i++)
            //{
            //    Interface::FITKGeoMeshVSPt* pt = pts[i];
            //    Interface::FITKGeoMeshVSPt* pt_2 = pts[i + 1];
            //    if (!pt || !pt_2)
            //    {
            //        continue;
            //    }

            //    int ptIndex = points->InsertNextPoint(pt->x(), pt->y(), pt->z());
            //    int ptIndex_2 = points->InsertNextPoint(pt_2->x(), pt_2->y(), pt_2->z());

            //    vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            //    cell->InsertNextId(ptIndex);
            //    cell->InsertNextId(ptIndex_2);
            //    cellIndex = ugrid->InsertNextCell(VTKCellType::VTK_LINE, cell);

            //    // Save the edge id.
            //    iData->CellIdEdgeIdMap.push_back(meshEdgeId);
            //    iData->EdgeIdCellIdsMap[meshEdgeId].push_back(cellIndex);
            //}
            //@}
        }
    }

    void FITKPartDataSetManager::addFaceGrid(Interface::FITKGeometryMeshVS* meshVS, PartGeomDataVTK* iData, int cmdId)
    {
        // Get or create the grid and the points.
        vtkPolyData* polyData = iData->DataSetFace;
        if (!polyData)
        {
            polyData = vtkPolyData::New();
            polyData->SetPoints(vtkSmartPointer<vtkPoints>::New());
            polyData->SetPolys(vtkSmartPointer<vtkCellArray>::New());
            iData->DataSetFace = polyData;
        }

        vtkPoints* points = polyData->GetPoints();

        // Get the start indice.
        int startIndexPt = points->GetNumberOfPoints();

        // Get all faces.
        const QHash<int, Interface::FITKGeoMeshVSFaceEntity*> facesHash = meshVS->getFaceItems();
        if (facesHash.isEmpty())
        {
            return;
        }

        QHashIterator<int, Interface::FITKGeoMeshVSFaceEntity*> iter(facesHash);

        int cellIndex = -1;

        int ptIndexStart = startIndexPt;

        while (iter.hasNext())
        {
            iter.next();

            int meshFaceId = iter.key();
            Interface::FITKGeoMeshVSFaceEntity* face = iter.value();

            // Get all points of the face.
            const QList<Interface::FITKGeoMeshVSPt*> & facePoints = face->getPoints();
            for (Interface::FITKGeoMeshVSPt* facePoint : facePoints)
            {
                points->InsertNextPoint(facePoint->x(), facePoint->y(), facePoint->z());
            }

            // Get face triangles.
            const QList<Interface::FITKGeoMeshVSTri*> & tris = face->getTriangles();

            for (Interface::FITKGeoMeshVSTri* tri : tris)
            {
                // Get points indice pf the triangle.
                const int* nodes = tri->getNode();

                // Get points index in VTK grid of vertice
                int ptIndex1 = ptIndexStart + nodes[0];
                int ptIndex2 = ptIndexStart + nodes[1];
                int ptIndex3 = ptIndexStart + nodes[2];

                // Add cell.
                vtkSmartPointer<vtkIdList> triCell = vtkSmartPointer<vtkIdList>::New();
                triCell->InsertNextId(ptIndex1);
                triCell->InsertNextId(ptIndex2);
                triCell->InsertNextId(ptIndex3);
                cellIndex = polyData->InsertNextCell(VTKCellType::VTK_TRIANGLE, triCell);

                // Save the face id.
                iData->CellIdFaceIdMap.push_back(meshFaceId);
                iData->FaceIdCellIdsHash[meshFaceId].push_back(cellIndex);
                iData->FaceCellIdCmdIdMap.push_back(cmdId);
            }

            // Update the point start index.
            ptIndexStart += facePoints.count();
        }
    }

    void FITKPartDataSetManager::initializeSolidFaceMap(Interface::FITKVirtualTopoManager* topoMgr, PartGeomDataVTK* iData)
    {
        // Get the solid manager.
        Interface::FITKShapeVirtualTopoManager* tpSolidMgr = topoMgr->getShapeVirtualTopoManager(Interface::FITKGeoEnum::VTopoShapeType::VSSolid);
        if (!tpSolidMgr)
        {
            return;
        }

        // Get all solid of the model.
        int nSolids = tpSolidMgr->getDataCount();
        for (int i = 0; i < nSolids; i++)
        {
            Interface::FITKAbsVirtualTopo* vSolid = tpSolidMgr->getDataByIndex(i);
            int solidId = vSolid->getDataObjectID();

            // Get all sub-shapes of the solid.( faces )
            int nSubShape = vSolid->getSubTopoCount();
            for (int j = 0; j < nSubShape; j++)
            {
                Interface::FITKAbsVirtualTopo* vFace = vSolid->getSubTopo(j);
                int faceId = vFace->getDataObjectID();

                // Save the ids.
                iData->SolidFaceIdsHash[solidId].push_back(faceId);

                // Insert the face-solid id.
                iData->FaceSolidIdMap[faceId] = solidId;
            }
        }
    }

    void FITKPartDataSetManager::calculatePointNormals(vtkPolyData* polyData)
    {
        if (!polyData)
        {
            return;
        }

        // For computing points nromals.
        //@{
        vtkSmartPointer<vtkPoints> tempPts = vtkSmartPointer<vtkPoints>::New();
        //@}

        int nPts = polyData->GetNumberOfPoints();

        vtkSmartPointer<vtkDoubleArray> pointsNormals = vtkSmartPointer<vtkDoubleArray>::New();
        pointsNormals->SetNumberOfComponents(3);
        pointsNormals->SetNumberOfTuples(nPts);
        pointsNormals->FillComponent(0, 0);
        pointsNormals->FillComponent(1, 0);
        pointsNormals->FillComponent(2, 0);

        int cellId = 0;
        vtkIdType npts = 0;
#if VTK_MAJOR_VERSION < 9
        vtkIdType* pts = nullptr;
#else
        const vtkIdType* pts = nullptr;
#endif

        // Add two double tuple 3.
        auto addDouble3 = [&](double* v1, double* v2, double* result)
        {
            for (int i = 0; i < 3; i++)
            {
                result[i] = v1[i] + v2[i];
            }
        };

        vtkPoints* points = polyData->GetPoints();
        
        double result[3]{ 0., 0., 0. };
        for (cellId = 0, polyData->GetPolys()->InitTraversal(); polyData->GetPolys()->GetNextCell(npts, pts); ++cellId)
        {
            tempPts->Reset();
            tempPts->SetNumberOfPoints(0);
            for (vtkIdType i = 0; i < npts; ++i)
            {
                tempPts->InsertNextPoint(points->GetPoint(pts[i]));
            }

            double norCellCurrent[3];
            vtkPolygon::ComputeNormal(tempPts, norCellCurrent);

            for (vtkIdType i = 0; i < npts; ++i)
            {
                int iPt = pts[i];
                double* norPtCurrent = pointsNormals->GetTuple3(iPt);

                // Add normals.
                addDouble3(norPtCurrent, norCellCurrent, result);
                pointsNormals->SetTuple3(iPt, result[0], result[1], result[2]);
            }
        }

        double flipDirection = 1.0;
        for (vtkIdType i = 0; i < nPts; ++i)
        {
            double* ptNormal = pointsNormals->GetTuple3(i);
            const double length = sqrt(pow(ptNormal[0], 2) + pow(ptNormal[1], 2) + pow(ptNormal[2], 2)) * flipDirection;
            if (!qFuzzyCompare(length, 0.))
            {
                ptNormal[0] /= length;
                ptNormal[1] /= length;
                ptNormal[2] /= length;
            }

            pointsNormals->SetTuple3(i, ptNormal[0], ptNormal[1], ptNormal[2]);
        }

        polyData->GetPointData()->SetNormals(pointsNormals);
    }

    void FITKPartDataSetManager::slot_partGeomDeleted(Core::FITKAbstractDataObject* objDel)
    {
        if (!objDel)
        {
            return;
        }

        int dataId = objDel->getDataObjectID();
        if (!m_partGeomDataHash.contains(dataId))
        {
            return;
        }

        // Delete the vtkDataSets.
        PartGeomDataVTK* data = m_partGeomDataHash.take(dataId);
        if (!data)
        {
            return;
        }

        if (data->DataSetVertex)
        {
            data->DataSetVertex->Delete();
        }

        if (data->DataSetEdge)
        {
            data->DataSetEdge->Delete();
        }

        if (data->DataSetFace)
        {
            data->DataSetFace->Delete();
        }

        // Delete the whole data.
        delete data;
    }
}